"""
Depth-first searching alone anomalies
"""
from typing import Dict
from typing import Sequence
from typing import Set

import numpy as np

from .base import Score
from .base import Scorer
from .common import pearson
from ..model.case import CaseData
from ..model.graph import Graph
from ..model.graph import Node


class DFSScorer(Scorer):
    """
    深度优先搜索评分器
    从SLI(服务级别指标)开始，通过图的深度优先搜索找出没有异常父节点的异常节点
    
    工作原理：
    1. 从SLI节点开始向上遍历依赖图
    2. 标记遍历过程中遇到的异常节点
    3. 找出那些没有异常父节点的异常节点作为根因
    """

    def __init__(self, anomaly_threshold: float, **kwargs):
        """
        初始化评分器
        
        Args:
            anomaly_threshold: 异常阈值，用于判断节点是否异常
            **kwargs: 传递给父类的额外参数
        """
        super().__init__(**kwargs)
        self._anomaly_threshold = anomaly_threshold

    def _extended(
        self, node: Node, parent: Node, anomalies: Set[Node], data: CaseData
    ) -> bool:
        """
        判断是否需要继续向父节点扩展搜索
        
        Args:
            node: 当前节点
            parent: 父节点
            anomalies: 异常节点集合
            data: 案例数据
        
        Returns:
            bool: 如果父节点异常则返回True，表示需要继续搜索
        """
        return parent in anomalies

    def score(
        self,
        graph: Graph,
        data: CaseData,
        current: float,
        scores: Dict[Node, Score] = None,
    ) -> Dict[Node, Score]:
        """
        执行评分过程
        
        工作流程：
        1. 根据阈值筛选出异常节点集合
        2. 从SLI开始进行层次遍历
        3. 对每层节点检查其父节点
        4. 找出没有异常父节点的异常节点
        
        Args:
            graph: 依赖图
            data: 案例数据
            current: 当前时间点
            scores: 前序评分器的评分结果
        
        Returns:
            Dict[Node, Score]: 根因节点及其评分
        """
        # 根据阈值筛选异常节点
        anomalies = {
            node
            for node, score in scores.items()
            if score.score >= self._anomaly_threshold
        }
        
        # 初始化搜索状态
        layer = {data.sli}        # 当前层节点集合，从SLI开始
        visited = {data.sli}      # 已访问节点集合
        roots: Set[Node] = set()  # 根因节点集合
        
        # 层次遍历
        while layer:
            next_layer: Set[Node] = set()  # 下一层节点集合
            # 处理当前层的每个节点
            for node in layer:
                is_root = True  # 假设当前节点是根因
                # 检查所有父节点
                for parent in graph.parents(node):
                    if parent in visited:  # 父节点已访问
                        is_root = False
                    elif self._extended(node, parent, anomalies, data):  # 父节点异常且未访问
                        is_root = False
                        visited.add(parent)
                        next_layer.add(parent)
                # 如果没有异常父节点，则为根因
                if is_root:
                    roots.add(node)
            # 更新当前层
            layer = next_layer

        # 返回根因节点的评分结果
        return {node: scores[node] for node in roots if node in scores}


class MicroHECLScorer(DFSScorer):
    """
    MicroHECL评分器
    扩展了DFSScorer，增加了基于相关性的异常传播判断
    
    特点：
    1. 通过时间序列相关性判断异常是否在节点间传播
    2. 当节点间相关性低于阈值时停止搜索
    3. 更精确地追踪异常传播路径
    
    注意：此实现不是线程安全的，因为使用了实例变量存储临时数据
    """

    def __init__(self, stop_threshold: float = 0.7, **kwargs):
        """
        初始化评分器
        
        Args:
            stop_threshold: 相关性阈值，默认0.7
                          当父子节点的相关性低于此值时停止搜索
            **kwargs: 传递给父类的参数
        """
        super().__init__(**kwargs)
        self._stop_threshold = stop_threshold
        self._data: Dict[Node, Sequence[float]] = None  # 临时存储节点的时间序列数据

    def _extended(
        self, node: Node, parent: Node, anomalies: Set[Node], data: CaseData
    ) -> bool:
        """
        判断异常是否从父节点传播到当前节点
        
        判断逻辑：
        1. 首先确认父节点是否异常
        2. 然后计算父子节点时间序列的相关性
        3. 只有当相关性超过阈值时才认为异常发生传播
        
        Args:
            node: 当前节点
            parent: 父节点
            anomalies: 异常节点集合
            data: 案例数据
        
        Returns:
            bool: True表示异常从父节点传播到了当前节点
        """
        # 如果父节点不是异常节点，直接返回False
        if parent not in anomalies:
            return False
        # 获取测试窗口内的时间序列数据
        series_a = np.array(self._data[node][-data.test_window :])
        series_b = np.array(self._data[parent][-data.test_window :])
        # 计算皮尔逊相关系数
        correlation = pearson(series_a, series_b)
        # 判断相关性是否超过阈值
        return correlation >= self._stop_threshold

    def score(
        self,
        graph: Graph,
        data: CaseData,
        current: float,
        scores: Dict[Node, Score] = None,
    ) -> Dict[Node, Score]:
        """
        执行评分过程
        
        工作流程：
        1. 加载所有节点的时间序列数据
        2. 调用父类的评分方法
        3. 清理临时数据
        
        Args:
            graph: 依赖图
            data: 案例数据
            current: 当前时间点
            scores: 前序评分器的评分结果
        
        Returns:
            Dict[Node, Score]: 根因节点及其评分
        """
        # 加载时间序列数据
        self._data = data.load_data(graph, current)
        # 调用父类的评分方法
        scores = super().score(graph=graph, data=data, current=current, scores=scores)
        # 清理临时数据
        self._data = None
        return scores
